package model;

import java.util.LinkedList;
import java.util.Random;
import engine.*;

/**
 * Instantiates agent logic for the humoral response cell, the B cell.
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-11
 */
public class BCell extends Cell
{
	private static final Parameter<Double> T1 =
		new Parameter<Double>(0, "bcell", "life-time", 70.);
	private static final Parameter<Double> T2 =
		new Parameter<Double>(0, "bcell", "internalize-time", 0.002);
	private static final Parameter<Double> T2_dev =
		new Parameter<Double>(0, "bcell", "internalize-time-dev", 0.001);
	private static final Parameter<Double> T3 =
		new Parameter<Double>(0, "bcell", "present-antigen-time", 0.015);
	private static final Parameter<Double> T3_dev =
		new Parameter<Double>(0, "bcell", "present-antigen-time-dev", 0.005);
	private static final Parameter<Double> T4 =
		new Parameter<Double>(0, "bcell", "divide-time", 0.3);
	private static final Parameter<Double> T4_dev =
		new Parameter<Double>(0, "bcell", "divide-time-dev", 0.02);
	private static final Parameter<Double> T5 =
		new Parameter<Double>(0, "bcell", "convert-time", 2.5);
	private static final Parameter<Double> T5_dev =
		new Parameter<Double>(0, "bcell", "convert-time-dev", 0.2);
	private static final Parameter<Double> T6 =
		new Parameter<Double>(0, "bcell", "produce-ab-time", 0.001);
	private static final Parameter<Double> T7 =
		new Parameter<Double>(0, "bcell", "plasma-death-time", 3.);
	private static final Parameter<Double> T7_dev =
		new Parameter<Double>(0, "bcell", "plasma-death-time-dev", 0.5);
	private static final Parameter<Double> P1 =
		new Parameter<Double>(0, "bcell", "plasma-else-memory-probability", 0.95);
	private static final Parameter<Long> NAB =
		new Parameter<Long>(0, "bcell", "produce-n-ab", 170000l);

	private enum MyState implements State {
		NAIVE, ACTIVATED, PLASMA, MEMORY, DEATH ;
		public String getName() { return toString(); }
		public int getNbr() { return ordinal(); }
	}

	public enum MyEvent implements StateEvent {
		NAIVE_DEATH, ACTIVATION, DIVIDE, CONVERT_PLASMA, CONVERT_MEMORY,
		PRODUCE_ANTIBODY, PLASMA_DEATH, BIND_VIRION, INTERNALIZE, PRESENT_ANTIGEN;
		public String getName() { return toString(); }
	}

	private static class MyStatemachine extends StatemachineModel<MyState,MyEvent> {
		public MyStatemachine() {
			super(MyState.class, MyEvent.class);
			// naive
			addTransition(MyState.NAIVE, MyEvent.NAIVE_DEATH, MyState.DEATH,
				(Statemachine sm, Object data) -> {
					Simulation.get().getAgentsLayer().remove((Cell)sm);
				}
			);
			// activated
			addTransition(MyState.ACTIVATED, MyEvent.DIVIDE, null,
				(Statemachine sm, Object data) -> {
					Simulation.get().getAgentsLayer().add(new BCell((BCell)sm));
					sm.issue(T4.get(), T4_dev.get(), MyEvent.DIVIDE, null);
				}
			);
			addTransition(MyState.ACTIVATED, MyEvent.CONVERT_PLASMA, MyState.PLASMA,
				(Statemachine sm, Object data) -> {
					sm.issue(T6.get(), MyEvent.PRODUCE_ANTIBODY, null);
					sm.issue(T7.get(), T7_dev.get(), MyEvent.PLASMA_DEATH, null);
				}
			);
			addTransition(MyState.ACTIVATED, MyEvent.CONVERT_MEMORY, MyState.MEMORY,
				null
			);
			// plasma
			addTransition(MyState.PLASMA, MyEvent.PRODUCE_ANTIBODY, null,
				(Statemachine sm, Object data) -> {
					Cell cell = (Cell)sm;
					Simulation.get().getFluidsLayer().secrete(cell.getX(), cell.getY(), NAB.get());
					sm.issue(T6.get(), MyEvent.PRODUCE_ANTIBODY, null);
				}
			);
			addTransition(MyState.PLASMA, MyEvent.PLASMA_DEATH, MyState.DEATH,
				(Statemachine sm, Object data) -> {
					Simulation.get().getAgentsLayer().remove((Cell)sm);
				}
			);
			// for all
			addTransition(null, MyEvent.BIND_VIRION, null,
				(Statemachine sm, Object data) -> {
					BCell cell = (BCell)sm;
					Integer virion = (Integer)data;
					cell.receptorBinding.add(virion);
					sm.issue(T2.get(), T2_dev.get(), MyEvent.INTERNALIZE, virion);
				}
			);
			addTransition(null, MyEvent.INTERNALIZE, null,
				(Statemachine sm, Object data) -> {
					BCell cell = (BCell)sm;
					Integer virion = (Integer)data;
					if(cell.receptorBinding.contains(virion)) {  // guard
						cell.internalized.add(virion);
						sm.issue(T3.get(), T3_dev.get(), MyEvent.PRESENT_ANTIGEN, virion);
				}
			});
			addTransition(null, MyEvent.PRESENT_ANTIGEN, null,
				(Statemachine sm, Object data) -> {
					BCell cell = (BCell)sm;
					Integer virion = (Integer)data;
					if(cell.internalized.contains(virion))  // guard
						cell.presenting.add(virion);
				}
			);
			addTransition(null, MyEvent.ACTIVATION, MyState.ACTIVATED,
				(Statemachine sm, Object data) -> {
					BCell cell = (BCell)sm;
					cell.convert((new Random().nextGaussian()*T5_dev.get())+T5.get());
				}
			);
		}
	}

	private void convert(double tc) {
		issue(T4.get(), T4_dev.get(), MyEvent.DIVIDE, null);
		if(Math.random() < P1.get())
			issue(tc, MyEvent.CONVERT_PLASMA, null);
		else
			issue(tc, MyEvent.CONVERT_MEMORY, null);
		clock_convert = Scheduler.clock() + tc;
	}

	private static MyStatemachine model = new MyStatemachine();
	private LinkedList<Integer> receptorBinding = new LinkedList<Integer>();
	private LinkedList<Integer> internalized = new LinkedList<Integer>();
	private LinkedList<Integer> presenting = new LinkedList<Integer>();
	private double clock_convert;  // clock when it will convert

	public BCell(BCell parent) {
		super(parent, model, (parent == null) ? MyState.NAIVE : MyState.ACTIVATED);
		Random r = new Random();
		if(parent == null) {
			x = r.nextDouble() * Model.getPetriWidth();
			y = r.nextDouble() * Model.getPetriHeight();
			issue(T1.get(), MyEvent.NAIVE_DEATH, null);
		}
		else {  // jitter around parent
			x = parent.x + Math.random()*50 - 25;
			y = parent.y + Math.random()*50 - 25;
			convert(parent.clock_convert - Scheduler.clock());
		}

	}

	public int getType() { return 0; }

	@SuppressWarnings("rawtypes")
	public static StatemachineModel getModel() { return model; }
}
